Skip to content

Userinterface windows abstraction#345

Open
lexm2 wants to merge 17 commits into
RawAccelOfficial:userinterfacefrom
lexm2:userinterface-windows-abstraction
Open

Userinterface windows abstraction#345
lexm2 wants to merge 17 commits into
RawAccelOfficial:userinterfacefrom
lexm2:userinterface-windows-abstraction

Conversation

@lexm2
Copy link
Copy Markdown
Member

@lexm2 lexm2 commented May 29, 2026

Overview

This refactors the userspace backend so it talks to the driver through a platform abstraction instead of directly against the Windows C++/CLI wrapper. Driver access and acceleration evaluation now live behind interfaces, with Windows as the first concrete implementation, and a new RawAccel.Contracts project holds the OS independent data shapes both sides share. The goal is to make the backend portable so a non-Windows (Linux) backend can plug in later.

What changed

Driver abstraction. Adds IRawAccelDriver and IAccelEvaluator, and moves all the Windows specific code under Driver/Windows/ (WindowsRawAccelDriver, ManagedAccelEvaluator, RawInputMouseListener, WindowsSystemDevicesRetriever).

RawAccel.Contracts. New project with OS independent config/profile types (RawAccelConfig, RawAccelProfile, RawAccelDeviceConfig, RawAccelAccelArgs, RawAccelSpeedArgs, Vec2, plus shared enums and constants), decoupling the backend and tests from the native struct layout.

Backend cleanup. BackEndComposer is heavily simplified, formula defaults are unified into a single FormulaDefaults, and profile name uniqueness is now enforced through a validator.

Live speed line. Mouse speed is captured live on Windows (RawInputMouseListener + MouseSpeedPollingService) to drive the current-speed line on the chart.

Testing

Backend test coverage was expanded and made to pass on non Windows, adding round trip, validation, and curve-preview disposal tests.

lexm2 added 17 commits May 24, 2026 22:56
Introduce IRawAccelDriver / IAccelEvaluator / ISystemDevicesRetriever behind the BackEnd, backed by the portable RawAccel.Contracts POCO project as the cross-OS JSON contract. Move the concrete Windows implementations (WindowsRawAccelDriver, ManagedAccelEvaluator, WindowsSystemDevicesRetriever) under Driver/Windows/. BackEndComposer registers the Windows impls; the curve preview and apply paths now talk to the interfaces.

Windows-only: contains no Linux backend, agent, or cross-OS build scaffolding. The Linux HID-BPF implementation lands in a follow-up PR layered on top of this one.
…id-bpf

Brings the backend hardening/cleanup and GUI work from
userinterface-linux-hid-bpf onto the Windows abstraction branch,
EXCLUDING the Linux driver implementations (userspace-backend/Driver/Linux/**).

Faithful tree match of RawAccel.Contracts, userspace-backend,
userspace-backend-tests, and userinterface to the hid-bpf branch, minus
the 6 Linux driver files.

Known dangling references to the excluded Linux classes (do not compile
until reconciled):
  - userspace-backend/BackEndComposer.cs  (Linux DI registration branch)
  - userspace-backend-tests/DisplayTests/CurvePreviewDisposalTests.cs
Make the transplanted backend compile without the excluded Linux driver
code (userspace-backend/Driver/Linux/**):

  - BackEndComposer: drop the Linux DI registration branch; platform
    switch is now Windows-or-throw (message updated to "Windows only").
  - CurvePreviewDisposalTests: remove AccelInstance_DisposeIsIdempotent
    (it instantiated LinuxAccelEvaluator); the 4 FakeEvaluator-based
    disposal-contract tests still cover the regression.
The BackEndApplyTests compose the real backend graph and were designed to
run on Windows and Linux by injecting their own IRawAccelDriver +
ISystemDevicesRetriever before Compose. They relied on the platform branch
to supply IAccelEvaluator (the now-excluded LinuxAccelEvaluator), so they
broke once the Linux driver code was dropped.

  - BackEndComposer: on non-Windows, register no platform defaults instead
    of throwing; callers inject what they need and a missing service fails
    at point of use, not at composition.
  - BackEndApplyTests: add an identity FakeAccelEvaluator and register it
    before each Compose so CurvePreview resolves without a platform driver.

70/70 backend tests pass on Linux; userspace-backend and userinterface build.
…ollisions

wrapper.vcxproj exposes Profile/AccelArgs/DeviceSettings/DeviceConfig/
AccelMode/CapMode/SpeedArgs as public C++/CLI types in the global
namespace. On Windows, where userspace-backend references the wrapper,
the using X = RawAccel.Contracts.RawAccelX; aliases introduced by the
Linux transplant collide with those global types (CS0576). Linux builds
weren't affected because wrapper isn't referenced there.

Prefix the aliases with Ra (RaProfile, RaAccelArgs, etc.) and update
in-file usages. Also:
  - WindowsRawAccelDriver: DriverConfig.Deactivate is static, call it
    directly instead of via GetDefault().
  - userspace-backend-tests: qualify bare Profile/AccelMode references
    that were silently resolving to the wrapper's globals.

Full solution now builds on Windows (driver.vcxproj still requires WDK).
userspace-backend-tests: 71/71 pass. wrapper-tests: 10/10 pass.
e6ae79e "Fix warnings" dropped the ex identifier from two
catch (Exception) blocks but left the , ex inner-exception argument
inside the rethrown ApplicationException, producing CS0103. Restore
the variable name.
…speed line

WindowsRawAccelDriver.GetCurrentMouseSpeedSample returned Zero, so the
chart speed line never moved on Windows (unlike Linux, where the agent
reports speed). Add RawInputMouseListener, which captures raw input
(WM_INPUT) on its own message-only window and thread and reports the
current speed in the chart units (counts/ms normalized to 1000 DPI).

Per-device DPI normalization joins each WM_INPUT handle to its
hardware-id via wrapper.dll MultiHandleDevice and looks up the applied
config DPI, defaulting for unconfigured handles. The driver creates the
listener lazily on first poll and disposes it via IDisposable.
Real fixes (not just cleanup):
- ManagedAccelEvaluator: dispose the seed ManagedAccel after
  CreateStatelessCopy so its native instance pair frees deterministically
  instead of waiting on the finalizer.
- RawInputMouseListener.UpdateDevices: per-device DPI fallback now uses
  the new config's default rather than the stale defaultDpi field.
- RawInputMouseListener: bail (not silently continue) when
  RegisterRawInputDevices fails so running reflects reality.
- RawInputMouseListener.FactorForHandle: drop the gate; handleFactors is
  volatile + build-once-publish so the WM_INPUT hot path is lock-free.
- WindowsRawAccelDriver.Apply: don't fail Apply when only the post-apply
  listener.UpdateDevices throws (driver is already active); also
  IsNullOrEmpty for the wrapper errors string.
- WindowsRawAccelDriver.Dispose: take listenerGate, flip a disposed flag,
  dispose outside the lock; closes a race where EnsureListener could
  resurrect a listener after Dispose nulled the field.

Cleanups:
- RawAccelConstants: add NormalizedDpi (mirrors common/rawaccel-base.hpp).
- WindowsSystemDevice: capture name/HWID strings directly (??= empty) and
  drop the dead RawDevice ref-rooting field.
- RawInputMouseListener: cache Marshal.SizeOf/OffsetOf results; drop the
  unused TranslateMessage call+import (message-only sink); upgrade setup
  failures to LogWarning and thread-loop catch to LogError; gate
  UnregisterClassW on a classRegistered flag; name LifecycleTimeoutMs;
  Volatile.Read/Write the cross-thread nativeThreadId.
- WindowsRawAccelDriver: mark listener/lastConfig volatile for the
  EnsureListener fast-path and the Apply -> EnsureListener cross-thread
  read.
The Ra-prefixed Contracts aliases (RaProfile, RaAccelArgs, etc.) are a
workaround for wrapper.cpp declaring its public C++/CLI types in the
global namespace, which collides on Windows (CS0576). Note in the
existing net8.0-migration TODO that namespacing the wrapper types is the
real fix and should land with that migration.
Comment blocks across the backend had been padded so every line landed at
roughly the same column, which left filler words behind. Strip the padding
and collapse the longest blocks (the wrapper-migration TODOs, the
ProfilesModel circular-dep TODO, several XML doc <remarks>) without
losing substance or test/file references.
…idator

Profile names were validated only for length/non-emptiness, so two
profiles could share a name (unlike mappings). Add ProfileNameValidator
mirroring MappingNameValidator: it checks case-insensitive uniqueness via
IProfilesModel.TryGetProfile plus the prior non-empty/max-length guard,
and register it as the keyed validator for the profile Name setting.

No DI cycle exists despite the removed TODO's premise: ProfilesModel's
ctor builds no ProfileModel instances, so the validator's IProfilesModel
dependency resolves against the already-built singleton.
State the shared mirror/JSON-contract rule once on RawAccelConfig instead of
repeating it (plus its 'do not rename' elaboration) on every type; reduce the
rest to a one-line wrapper.cpp pointer. Replace the version comment with a TODO
to unify version bumps (1.7.1 vs driver-still-1.7.0 mismatch).
Collapse multi-line/padded comments (and the ====-banner dividers unique
to ProfileChartViewModel) down to terse 1-3 line notes, matching the
single-line comment style used across the rest of the codebase. Drop the
repeated "reassign fresh Sections so LiveCharts redraws" rationale to a
single statement on the Sections property. No behavior change.
Default values for acceleration-formula settings were defined twice: as the
EditableSetting initial values in BackEndComposer DI registration, and as
auto-property initializers in the Data DTOs (only NaturalAccel had these).

Add a single FormulaDefaults const class referenced by both. The five DTOs
that previously lacked initializers now fall back to the proper default
instead of 0 when a field is absent from partial JSON; fully-written profiles
are unaffected. FormulaDefaults is intentionally independent of native/Contracts.
… types

Adds Classic and Linear DataRows to DeserializeFormulaAccel_AllSubtypes
so the parameterized test covers every formula type, and trims the
three-line regression comment to one line.
Restore original comment wording in ProfileChartViewModel, Program,
App.axaml, BackEndApplyTests, EditableSetting, EditableSettingsCollection,
EditableSettingsSelector, IEditableSetting, ProfileModel, and SystemDevices.

Also extend .gitignore to cover linux/build-*/, linux/target/, and Testing/
so CMake/Rust build artifacts are never staged again.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant